/**
 * Rabbits came with humans. They pose risk to island flora
 * - when not doing anything else, they eat grass
 * - when they are sated and not scared, they breed
 * - they are quite lazy, so won't look for anything beyond their fov unless hungry
 * - sleep behaviour to be done later
 * - they can catch and spread fire
 * - when scared, they can also jump
 */

import { AI, Creature, Description, Glyph, type Entity, Impassable, Collision } from '$lib/components';
import { defineComponent, hasComponent, Types, type IWorld, addComponent, removeComponent } from '$lib/ecs';
import { Hunger } from './hunger';
import { registerAI, type Action } from '$lib/simulate';
import { Clock } from '$lib/clock';
import { Heart, withHeartBeats } from '$lib/content/heart';
import { Eyes, getEyeRange, processEyes3 } from '$lib/eyes';
import { distribute, random } from '$lib/random';
import { logVisible } from '$lib/render3';
import { describe } from '$lib';
import { getPos3, landscapeAt, move, onTile } from '$lib/pos3';
import { Foliage, NeedSwim, Water, mowGrass } from './ground';
import { Bush, mowLeaves } from './foliage';
import { Move3Action } from '$lib/walk3';
import { findStepPath2D } from '$lib/path';

export const Rabbit = defineComponent('Rabbit', {});

/// TODO: Move to generic AI lib
export const ExploreTarget = defineComponent('ExploreTarget', {
  x: Types.i32,
  y: Types.i32,
});

export const rabbitTemplate = [
  [Creature],
  [Rabbit],
  [Glyph, {
    glyph: 'y',
    color: '#eee',
  }],
  [Description, {
    summary: "rabbit",
    description: "A medium-sized animal with long ears. You never saw them until recently and have little idea of what they are capable of. Still, they don't look intimidating.",
  }],
  [AI],
  [Clock],
  [Heart, {
    heartbeat: 10,
    elapsed: 0,
  }],
  [Hunger, {
    satiation: 0,
    tick: 1,
  }],
  [Eyes, {
    range: 8,
  }],
];

const runAwayFromThreat: Action = (world, e) => {
  return {
    error: false,
    action: withHeartBeats(1, e, () => {
      logVisible(e, `${describe(world, e)} is scared and tries to run away`);
    }),
  };
};

const feedHere = (hasGrass: boolean, ground: Entity, bushes: Entity[]) => (world, e) => {
  return {
    error: false,
    action: withHeartBeats(1, e, () => {
      const wantToEat = Math.floor(random()*3+1);
      logVisible(e, `${describe(world, e)} wants to eat ${wantToEat}`);
      let amount;
      if (hasGrass && (random() > 0.5 || bushes.length === 0)) {
        amount = mowGrass(world, ground, wantToEat);
      } else {
        const ix = Math.floor(bushes.length * random());
        amount = mowLeaves(world, bushes[ix], wantToEat);
      }
      Hunger.satiation[e] += 10*amount;
    }),
  };
};

const rabbitCanPass = (world, es) => {
  const hasCreature = es.some((e) => hasComponent(world, Creature, e));
  if (hasCreature)
    return false;
  const groundPass = es.every((e) =>
    !hasComponent(world, NeedSwim, e) &&
    !hasComponent(world, Impassable, e) &&
    !hasComponent(world, Collision, e));
  return es.length > 0 && groundPass;
};

const RabbitMove = (dx, dy, dz) => (world, e) => {
  const delay = Heart.heartbeat[e];
  return Move3Action(rabbitCanPass)(dx, dy, dz, delay)(world, e);
};

const findFood = (world, e) => {
  return {
    error: false,
    action: withHeartBeats(1, e, () => {
      logVisible(e, `${describe(world, e)} looks for food`);
      // oh, are you trying to understand what this code does? good luck!
      // if you do, might as well keep it as to pass the torch
      const vision = processEyes3(world, e);
      const dirScore = new Map();
      for (let i = 1; i <= getEyeRange(world, e); ++i) {
        const sideQ = [[-1, -1, 1, 0], [1, -1, 0, 1], [1, 1, -1, 0], [-1, 1, 0, -1]];
        for (let side = 0; side < 4; ++side) {
          const [x0, y0, qx, qy] = sideQ[side];
          for (let j = 0; j < 2*i; ++j) {
            const x = x0*i + qx*j;
            const y = y0*i + qy*j;
            const es = vision.get(y)?.get(x)?.stack ?? [];
            const score = es.reduce(
              (s, e) => s +
                (hasComponent(world, Foliage, e) ? Foliage.amount[e] : 0) +
                (hasComponent(world, Bush, e) ? 10 : 0),
              0
            ) / Math.sqrt(i);
            const offsetFromCenter = j - i;
            const centered = Math.abs(offsetFromCenter) <= (i-1)/2;
            const halfCentered = Math.abs(offsetFromCenter) < (i+1)/2;
            let dirs = [];
            if (halfCentered) {
              dirs.push(side*2);
            }
            if (!centered && offsetFromCenter < 0) {
              dirs.push((8 + (side*2-1)) % 8); // why -1 % 8 != 7, ugh
            } else if (!centered && offsetFromCenter > 0) {
              dirs.push(side*2+1);
            }
            for (let dir of dirs) {
              const s = dirScore.get(dir) ?? 0;
              dirScore.set(dir, s + score/dirs.length);
            }
          }
        }
      }
      const dirs = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]];
      const dirsWithScore = Array.from(dirScore).map(([n, score]) =>
        [dirs[n], score]
      ).filter(([dir, _]) => {
        const [dx, dy] = dir;
        const es = vision.get(dy)?.get(dx)?.stack ?? [];
        return rabbitCanPass(world, es);
      });
      if (dirsWithScore.length === 0) {
        logVisible(e, `${describe(world, e)} wants to find food, but couldn't find where to go!`);
        return;
      }
      const [dx, dy] = distribute(random(), dirsWithScore);
      return Move3Action(rabbitCanPass)(dx, dy, 0, 0)(world, e).action();
    }),
  };
};

const randomExploreTarget = (world, e, x, y) => {
  const dist = getEyeRange(world, e);
  const dx = Math.floor(random()*(dist*2+1)) - dist;
  const dy = Math.floor(random()*(dist*2+1)) - dist;
  // NOTE that this may return (0, 0)
  addComponent(world, ExploreTarget, e);
  ExploreTarget.x[e] = x+dx;
  ExploreTarget.y[e] = y+dy;
};

const explore: Action = (world: IWorld, e: Entity) => {
  return {
    error: false,
    action: withHeartBeats(1, e, () => {
      logVisible(e, `${describe(world, e)} explores the world`);
      let dir;
      let retry = 10;
      const {x, y, z} = getPos3(e);
      while (retry > 0 && !dir || dir === true) {
        if (!hasComponent(world, ExploreTarget, e)) {
          randomExploreTarget(world, e, x, y);
        }
        const x1 = ExploreTarget.x[e];
        const y1 = ExploreTarget.y[e];
        // TODO: use relevant vision instead of generic map
        dir = findStepPath2D(world, rabbitCanPass, getEyeRange(world, e), z, x, y, x1, y1);
        if (dir === true || dir === false) {
          removeComponent(world, ExploreTarget, e);
        }
        --retry;
      }
      if (dir === false) {
        logVisible(e, `${describe(world, e)} is lost`);
        return;
      }
      move(world, e, x+dir[0], y+dir[1], z);
    }),
  };
};

registerAI({
  target: Rabbit,
  action: (world: IWorld, e: Entity) => {
    const {x, y, z} = getPos3(e);
    const motivations: [any, number][] = [];
    const scared = 0;
    const fearCoeff = Math.max(0, scared**3 * 100);
    if (scared > 0) {
      // TODO: jump
      motivations.push([
        runAwayFromThreat,
        fearCoeff,
      ]);
    }
    const here = onTile(world, x, y, z);
    const ground = landscapeAt(world, x, y, z);
    const hasGrass = (hasComponent(world, Foliage, ground) && Foliage.amount[ground] > 0)
    const bushes = here.filter((e) => hasComponent(world, Bush, e))
    const hungerCoeff = 0.25 * Math.max(0, (200 - Hunger.satiation[e]));
    if (hasGrass || bushes.length > 0) {
      motivations.push([
        feedHere(hasGrass, ground, bushes),
        hungerCoeff,
      ]);
    } else {
      motivations.push([
        findFood,
        hungerCoeff*0.5,
      ]);
    }
    if (scared <= 0) {
      motivations.push([
        explore,
        5,
      ]);
    }
    return distribute(random(), motivations)(world, e);
  },
});
